Custom Visual 1.0 Example

function MyCustomVisual() {

// this function will be called before rendering the next batch of visual chunks
    this.onBeforeRender = function (api) {

    };

    // this function should return array of strings, each string
    // represent a path of a library you want to import  
    this.importPaths = function () {

    };

    //This function should render single chunk visual
    this.renderChunk = function (element, chunkData, api) {

        var margin = 20;
        var categoriesWidth = 80;

        // In the next two lines we're calling the api size section in order to get the screen height and width
        var height = api.size.height - margin * 2;
        var width = api.size.width - margin * 2;

        // Get the d3 library in order to perform dom manipulations and paint the heat-map
        var d3 = api.deps.d3;
        // Get the list of the drop zones types (e.g. Columns, Rows, Values, Size etc...)
        var dropZones = api.data.dropZones;

        // Using the groupBy method in order to get the data points for the Columns and the Rows
        var colsData = api.data.groupBy(chunkData, dropZones.Columns);
        var rowsData = api.data.groupBy(chunkData, dropZones.Rows);

        var colsLength = Object.keys(colsData).length;
        var rowsLength = Object.keys(rowsData).length;

        var itemSizeWidth = (width - categoriesWidth) / colsLength;
        var cellSizeWidth = itemSizeWidth - 1;
        var itemSizeHeight = (height - categoriesWidth) / rowsLength;
        var cellSizeHeight = itemSizeHeight - 1;

        var xScale = d3.scale.scaleBand()
            .domain(Object.keys(colsData))
            .range([0, width - categoriesWidth]);

        var yScale = d3.scale.scaleBand()
            .domain(Object.keys(rowsData))
            .range([0, height - categoriesWidth]);

        var xAxis = d3.axis.axisTop(xScale);
        var yAxis = d3.axis.axisLeft(yScale);

        // y-axis paint section
        var yAxisSelection = d3.selection.select(element).selectAll('g.y-axis').data([null]);
        yAxisSelection.exit().remove();
        yAxisSelection = yAxisSelection
            .enter()
            .append('g')
            .attr('class', 'y-axis')
            .attr('transform', 'translate(95,100)')
            .merge(yAxisSelection)
            .call(yAxis);

        // x-axis
        var xAxisSelection = d3.selection.select(element).selectAll('g.x-axis').data([null]);
        xAxisSelection.exit().remove();
        xAxisSelection = xAxisSelection
            .enter()
            .append('g')
            .attr('class', 'x-axis')
            .attr('transform', 'translate(100, 95)')
            .merge(xAxisSelection)
            .call(xAxis)
            .selectAll('text')
            .attr("x", 8)
            .attr("y", -2)
            .attr("transform", "rotate(-65)")
            .style("text-anchor", "start");

        // Get heat map data 
        var fullData = [];
        Object.keys(colsData).forEach(function (colData, i) {
            var colDataPoints = colsData[colData].dataPoints;
            colDataPoints.forEach(function (dp, j) {
                // In the next two lines we are using the datapoints we got earlier from the "groupBy" method
                // in order to  get the columns and rows name.
                var colName = api.data.getDataPointDropZoneCaption(dp, dropZones.Columns);
                var rowName = api.data.getDataPointDropZoneCaption(dp, dropZones.Rows);
                
                // Using the dataPoint we got earlier to get the specific cell value
                var value = api.data.getDataPointValue(dp);
                fullData.push({
                    x: i,
                    y: j,
                    dataPoint: dp,
                    colName: colName,
                    rowName: rowName,
                    value: value
                });
            });
        });

        // Data Heat Map
        var groupSelection = d3.selection.select(element).selectAll('g.data').data([null]);

        groupSelection.exit().remove();

        groupSelection = groupSelection
            .enter()
            .append('g')
            .attr('class', 'data')
            .attr('transform', 'translate(100,100)')
            .merge(groupSelection);

        var rectSelection = d3.selection.select(element).select('g.data').selectAll('rect').data(fullData);
        rectSelection.exit().remove();
        rectSelection = rectSelection
            .enter()
            .append('rect')
            .merge(rectSelection)
            .attr('width', cellSizeWidth)
            .attr('height', cellSizeHeight)
            .attr('x', function (d) {
                return d.x * itemSizeWidth;
            })
            .attr('y', function (d) {
                return d.y * itemSizeHeight;
            })
            .attr('fill', function (d) {
                // Using getColor function with the datapoint we got earlier to get the cell color (given "black" as a default color)
                return api.data.getColor(d.dataPoint, 'black');
            })
            .on('mouseover', function (d) {
                // Using the interaction API "showTooltip" to activate the cell tooltip
                api.interaction.showTooltip(d.dataPoint)
            })
            .on('mouseout', function (d) {
                // Using the interaction API 'hideTooltip" function to deactivate the cell tooltip
                api.interaction.hideTooltip()
            })
            .on('contextmenu', function (d) {
                // Using the interaction API "showDatapointContextMenu" function to let right clicking the cell open the cell menu
                api.interaction.showDatapointContextMenu(d.dataPoint, d3.selection.event);
            })
            .on("click", function (d) {
                // Using the interaction API "selectDatapoint" function to make the cell clickable
                api.interaction.selectDatapoint(d.dataPoint, d3.selection.event)
            });
    }

    //This function will be called for each data change
    this.onDataChanged = function (fullData, api) {

    };

    // this function will be called for every theme or style change
    this.onThemeOrStyleChanged = function (api) {

    };

    // this function should return css string (if any) for custom style
    this.getCustomStyle = function (api) {
        // Using the style API in order to get the x&y axis style color
        var xAxisColor = api.style.styles.visuals.visualsXAxis.color;
        var yAxisColor = api.style.styles.visuals.visualsYAxis.color;

        var result = '.my-class {direction:rtl;}' +
            'g.x-axis text {' +
            'fill: ' + xAxisColor + ';}' +
            'g.x-axis path, line {' +
            'stroke: ' + xAxisColor + ';}' +
            'g.y-axis text {' +
            'fill: ' + yAxisColor + ';}' +
            'g.y-axis path, line {' +
            'stroke: ' + yAxisColor + ';}';

        return result;
    };

    this.init = function () { };

    // This line will allow debugging your Custom visual in your browser debugger
    //# sourceURL=MyCustomVisual.js
}